package org.seckill.service;

import java.util.Date;
import java.util.List;

import org.seckill.dao.SeckillDao;
import org.seckill.dao.SuccessKilledDao;
import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.entity.Seckill;
import org.seckill.entity.SuccessKilled;
import org.seckill.enums.SeckillStateEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.exception.SeckillException;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import ch.qos.logback.classic.Logger;

//@Component @Service @Dao @Controller
@Service
public class SeckillServiceImpl implements SeckillService {

	private Logger logger = (Logger) LoggerFactory.getLogger(this.getClass());

	//注入service依赖
	//@Autowired @Resource @Inject
	@Autowired
	private SeckillDao seckillDao;
	@Autowired
	private SuccessKilledDao successKilledDao;

	//盐值，只用混淆
	private final String salt = "riep29M;a.epehi8ein;]wqe-qmmvfjofsdll";

	public List<Seckill> getSeckillList() {
		return seckillDao.queryAll(0, 4);
	}

	public Seckill getSeckillById(long seckillId) {
		return seckillDao.queryById(seckillId);
	}

	public Exposer exportSeckillUrl(long seckillId) {
		//秒杀不存在
		Seckill seckill = getSeckillById(seckillId);
		if(seckill == null){
			return new Exposer(false, seckillId);
		}

		//秒杀还没开始或已结束
		Date startTime = seckill.getStartTime();
		Date endTime = seckill.getEndTime();
		Date nowTime = new Date();

		if(startTime.getTime() > nowTime.getTime()
				|| endTime.getTime() < nowTime.getTime()){
			return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(), endTime.getTime());
		}

		//转化特定字符串的过程，不可逆
		String md5 = getMD5(seckillId);
		return new Exposer(true,seckillId, md5);
	}

	//有复用，所以将其抽取成一个方法
	private String getMD5(long seckillId){
		String base = seckillId + "/" + salt;
		String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
		return md5;
	}

	/**
	 * 使用注解控制事务的优点
	 * 1：明确标注事务方法的风格
	 * 2：保证事务方法的执行时间尽可能短，不要穿插其他的网络操作（RPC HTTP请求等），或者剥离到事务方法外部
	 * 3: 不是方法的方法都需要事务，只有一条修改操作或者只有查询操作不需要事务控制
	 */
	@Transactional
	public SeckillExecution executeSeckill(long seckillId, long userPhone,
			String md5) throws SeckillException, RepeatKillException,
			SeckillCloseException {
		//md5为空或者被篡改，抛出异常
		if(md5==null || !md5.equals(getMD5(seckillId))){
			throw new SeckillException("seckill date rewrite");
		}

		//执行秒杀：减库存 + 记录秒杀明细

		try {
			Date nowTime = new Date();
			int reduceCount = seckillDao.reduceNumber(seckillId, nowTime);
			if(reduceCount <= 0){
				//没有更新到表示库存没有或者时间过了，都是秒杀结束
				throw new SeckillCloseException("seckill closed");
			}else{
				//记录购买行为
				int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone);
				if(insertCount <= 0){
					throw new RepeatKillException("seckill repeated");
				}else{
					//秒杀成功
					SuccessKilled SuccessKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone);
					return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, SuccessKilled);
				}
			}
		} catch(SeckillCloseException e1){
			//如果异常嵌套，可能会有覆盖现象
			throw e1;
		}catch(RepeatKillException e2){
			throw e2;
		}
		catch (Exception e) {
			logger.error(e.getMessage(), e);
			//所有编译期异常转化给运行期异常（数据会回滚）
			throw new SeckillException("seckill inner error" + e.getMessage());
			//			e.printStackTrace();
		}
	}
}
